// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/run_loop.h"
#include "base/test/test_message_loop.h"
#include "media/base/fake_audio_render_callback.h"
#include "media/base/mock_audio_renderer_sink.h"
#include "media/base/silent_sink_suspender.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

ACTION_P(RunClosure, closure)
{
    closure.Run();
}

class SilentSinkSuspenderTest : public testing::Test {
public:
    SilentSinkSuspenderTest()
        : params_(AudioParameters::AUDIO_FAKE,
            CHANNEL_LAYOUT_MONO,
            44100,
            8,
            128)
        , mock_sink_(new testing::StrictMock<MockAudioRendererSink>())
        , fake_callback_(0.1, params_.sample_rate())
        , temp_bus_(AudioBus::Create(params_))
        ,
        // Set a negative timeout so any silence will suspend immediately.
        suspender_(&fake_callback_,
            base::TimeDelta::FromSeconds(-1),
            params_,
            mock_sink_,
            test_loop_.task_runner())
    {
    }
    ~SilentSinkSuspenderTest() override { }

protected:
    base::TestMessageLoop test_loop_;
    const AudioParameters params_;
    scoped_refptr<testing::StrictMock<MockAudioRendererSink>> mock_sink_;
    FakeAudioRenderCallback fake_callback_;
    std::unique_ptr<AudioBus> temp_bus_;
    SilentSinkSuspender suspender_;

private:
    DISALLOW_COPY_AND_ASSIGN(SilentSinkSuspenderTest);
};

TEST_F(SilentSinkSuspenderTest, BasicPassthough)
{
    temp_bus_->Zero();
    auto delay = base::TimeDelta::FromMilliseconds(20);
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(delay, base::TimeTicks(), 0, temp_bus_.get()));

    // Delay should remain.
    EXPECT_EQ(delay, fake_callback_.last_delay());
    EXPECT_FALSE(temp_bus_->AreFramesZero());
}

TEST_F(SilentSinkSuspenderTest, SuspendResumeTriggered)
{
    // Verify a normal Render() doesn't invoke suspend.
    EXPECT_FALSE(suspender_.is_using_fake_sink_for_testing());
    temp_bus_->Zero();
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_FALSE(temp_bus_->AreFramesZero());
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(suspender_.is_using_fake_sink_for_testing());

    // Mute all audio generated by the callback, this should suspend immediately.
    fake_callback_.set_volume(0);
    temp_bus_->Zero();
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_TRUE(temp_bus_->AreFramesZero());
    {
        base::RunLoop run_loop;
        EXPECT_CALL(*mock_sink_, Pause())
            .WillOnce(RunClosure(run_loop.QuitClosure()));
        run_loop.Run();
        EXPECT_TRUE(suspender_.is_using_fake_sink_for_testing());
    }

    // Unmute the audio, the FakeWorker inside |suspender_| should be running now,
    // so we don't need to manually invoke Render().
    fake_callback_.reset();
    fake_callback_.set_volume(1);
    {
        base::RunLoop run_loop;
        EXPECT_CALL(*mock_sink_, Play())
            .WillOnce(RunClosure(run_loop.QuitClosure()));
        run_loop.Run();
        EXPECT_FALSE(suspender_.is_using_fake_sink_for_testing());
    }

    // The first Render() after resume should return the first buffer which was
    // not silent.
    fake_callback_.reset();
    std::unique_ptr<AudioBus> true_bus = AudioBus::Create(params_);
    fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
        true_bus.get());
    EXPECT_FALSE(true_bus->AreFramesZero());
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus->channel(0),
                  temp_bus_->frames() * sizeof(float)),
        0);
}

TEST_F(SilentSinkSuspenderTest, MultipleSuspend)
{
    // Mute all audio generated by the callback, this should suspend immediately.
    fake_callback_.set_volume(0);
    temp_bus_->Zero();
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_TRUE(temp_bus_->AreFramesZero());

    // A second render should only result in a single Pause() call.
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));

    EXPECT_CALL(*mock_sink_, Pause());
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(suspender_.is_using_fake_sink_for_testing());
}

TEST_F(SilentSinkSuspenderTest, MultipleResume)
{
    // Mute all audio generated by the callback, this should suspend immediately.
    fake_callback_.set_volume(0);
    temp_bus_->Zero();
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_TRUE(temp_bus_->AreFramesZero());
    EXPECT_CALL(*mock_sink_, Pause());
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(suspender_.is_using_fake_sink_for_testing());

    // Unmute the data.
    fake_callback_.set_volume(1);

    // Prepare our equality testers.
    fake_callback_.reset();
    std::unique_ptr<AudioBus> true_bus1 = AudioBus::Create(params_);
    fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
        true_bus1.get());
    std::unique_ptr<AudioBus> true_bus2 = AudioBus::Create(params_);
    fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
        true_bus2.get());
    EXPECT_NE(memcmp(true_bus1->channel(0), true_bus2->channel(0),
                  true_bus1->frames() * sizeof(float)),
        0);

    // Reset the fake callback data generation and force two Render() calls before
    // the sink can transition.
    fake_callback_.reset();
    EXPECT_EQ(
        temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0, nullptr));
    EXPECT_EQ(
        temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0, nullptr));
    EXPECT_CALL(*mock_sink_, Play());
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(suspender_.is_using_fake_sink_for_testing());

    // Each render after resuming should return one of the non-silent bus.
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus1->channel(0),
                  temp_bus_->frames() * sizeof(float)),
        0);
    EXPECT_EQ(temp_bus_->frames(),
        suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
            temp_bus_.get()));
    EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus2->channel(0),
                  temp_bus_->frames() * sizeof(float)),
        0);
}

} // namespace content
